package visitor;

import syntaxtree.*;
import semantic.*;
import semantic.Class;

public class TypeChecker implements TypeVisitor {

	Table table = null;
	private Class currClass = null;
	private Method currMethod = null;
  
  public TypeChecker(Table t) {
	  table = t;
  }
  
  // MainClass m;
  // ClassDeclList cl;
  public void visit(Program n) {
	n.m.accept(this);
	for ( int i = 0; i < n.cl.size(); i++ ) {
	    n.cl.elementAt(i).accept(this);
	}
  }
  
  // Identifier i1,i2;
  // Statement s;
  public void visit(MainClass n) {
    n.i1.accept(this);
    n.i2.accept(this);
    n.s.accept(this);
  }
  
  // Identifier i;
  // VarDeclList vl;
  // MethodDeclList ml;
  public void visit(ClassDeclSimple n) {

    n.i.accept(this);
    
	currClass = table.get(n.i);
    
    for ( int i = 0; i < n.vl.size(); i++ ) {
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.ml.size(); i++ ) {
        n.ml.elementAt(i).accept(this);
    }
  }
 
  // Identifier i;
  // Identifier j;
  // VarDeclList vl;
  // MethodDeclList ml;
  public void visit(ClassDeclExtends n) {

    n.i.accept(this);
    n.j.accept(this);
    
    currClass = table.get(n.i);
    
    for ( int i = 0; i < n.vl.size(); i++ ) {
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.ml.size(); i++ ) {
        n.ml.elementAt(i).accept(this);
    }
  }

  // Type t;
  // Identifier i;
  public void visit(VarDecl n) {
    n.t.accept(this);   
    if (n.t instanceof IdentifierType)
		((IdentifierType)n.t).accept(this);			
  }

  // Type t;
  // Identifier i;
  // FormalList fl;
  // VarDeclList vl;
  // StatementList sl;
  // Exp e;
  public void visit(MethodDecl n) {

    n.t.accept(this);
	
    n.i.accept(this);
    
    currMethod = currClass.methods.get(n.i);
    
    for ( int i = 0; i < n.fl.size(); i++ ) {
        n.fl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.vl.size(); i++ ) {
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.sl.size(); i++ ) {
        n.sl.elementAt(i).accept(this);
    }
    n.e.accept(this);
    
    currMethod = null;
  }

  // Type t;
  // Identifier i;
  public void visit(Formal n) {
    n.t.accept(this);
    if (n.t instanceof IdentifierType)
		((IdentifierType)n.t).accept(this);
  }

  public void visit(IntArrayType n) {
  }

  public void visit(BooleanType n) {
  }

  public void visit(IntegerType n) {
  }

  // String s;
  public void visit(IdentifierType n) {
	  if (!table.contains(new Identifier(n.s, 0, 0))){
		  String s = "Undefined class " + n.s + " at line " + n.line + ", column " + n.column;
		  Errors.complain(s);
	  }
  }

  // StatementList sl;
  public void visit(Block n) {

    for ( int i = 0; i < n.sl.size(); i++ ) {
        n.sl.elementAt(i).accept(this);
    }
  }
  
  // Exp e;
  // Statement s1,s2;
  public void visit(If n) {
	Type t = n.e.accept(this);
	if (t != null){
	    if(!(t  instanceof BooleanType)){
	    	String s = "Invalid argument at line " + n.e.line + ", column " + n.e.column + ": boolean expected";
	    	Errors.complain(s);
	    }
	}
    n.s1.accept(this);
    n.s2.accept(this);
  }

  // Exp e;
  // Statement s;
  public void visit(While n) {
	  Type t = n.e.accept(this);
	  /*if (t == null)
		  return;*/
	  if(!(t  instanceof BooleanType)){
		  String s = "Invalid argument at line " + n.e.line + ", column " + n.e.column + ": boolean expected";
		  Errors.complain(s);
	  }
	  n.s.accept(this);
  }

  //Exp e
  public void visit(Print n) {
    Type t = n.e.accept(this);
   if (t==null)
		return;
    if(!(t  instanceof IntegerType)){
		String s = "Invalid argument at line " + n.e.line + ", column " + n.e.column + ": int expected";
    	Errors.complain(s);
    }
  }
  
  // Identifier i;
  // Exp e;
  public void visit(Assign n) {
	  Type t1 = null;
	  if(currClass.containsField(n.i)){
		  t1 = currClass.getField(n.i);
	  }
	  else if(currClass instanceof ClassExtended){
		  if(((ClassExtended)currClass).father.containsField(n.i)) {
			  t1 = ((ClassExtended)currClass).father.getField(n.i);
		  }
	  }
	  else if(currMethod.containsLocal(n.i)) {
		  t1 = currMethod.getLocal(n.i);
	  }
	  else if(currMethod.containsParam(n.i)) {
		  t1 = currMethod.getParam(n.i);
	  }
	  
	  if(t1 == null) {
		  String s = "Invalid use of " + n.i.s + " at line " + n.i.line + ", column " + n.i.column + ": variable not declared";
	    	Errors.complain(s);
	  }
	  Type t2 = n.e.accept(this);
	  if(!(t1.getClass() == t2.getClass())){
		  String s = "Invalid assign at line " + n.e.line + ", column " + n.e.column + ": incompatible types";
		  Errors.complain(s);
	  }
  }

  // Identifier i;
  // Exp e1,e2;
  public void visit(ArrayAssign n) {
	  
	  Type t = null;
	  if(currClass.containsField(n.i)){
		  t = currClass.getField(n.i);
	  }
	  else if(currClass instanceof ClassExtended){
		  if(((ClassExtended)currClass).father.containsField(n.i)) {
			  t = ((ClassExtended)currClass).father.getField(n.i);
		  }
	  }
	  else if(currMethod.containsLocal(n.i)) {
		  t = currMethod.getLocal(n.i);
	  }
	  else if(currMethod.containsParam(n.i)) {
		  t = currMethod.getParam(n.i);
	  }
	  
	  if(t == null) {
		  String s = "Invalid use of " + n.i.s + " at line " + n.i.line + ", column " + n.i.column + ": variable not declared";
	    	Errors.complain(s);
	  }
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);

	  if ( !(t1 instanceof IntegerType) ) {
		  String s = "Invalid assign at line " + n.e1.line + ", column " + n.e1.column + ": int expected at index";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof IntegerType)){
		  String s = "Invalid assign at line " + n.e2.line + ", column " + n.e2.column + ": int expected";
		  Errors.complain(s);
	  }
  /*
	  
    Type i = n.i.accept(this);
    n.e1.accept(this);
    Type e2 = n.e2.accept(this);
    if(i != e2){
    	System.out.println("Tipos incompativeis para atribuição. Lado esquerdo da atribuição deve ser do mesmo tipo que lado direito.");
    }*/
  }

  // Exp e1,e2;
  public Type visit(And n) {
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);
	 /* if ((t1==null) || (t2==null))
		  return new BooleanType();*/
	  if (!(t1 instanceof BooleanType)) {
		  String s = "Invalid expression at line " + n.e1.line + ", column " + n.e1.column + ": boolean expected";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof BooleanType)) {
		  String s = "Invalid expression at line " + n.e2.line + ", column " + n.e2.column + ": boolean expected";
		  Errors.complain(s);
	  }
	  return new BooleanType();	
  }

  // Exp e1,e2;
  public Type visit(LessThan n) {
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);
	 /* if ((t1==null) || (t2==null)) {
		  return new IntegerType();
	  }*/
	  if (!(t1 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e1.line + ", column " + n.e1.column + ": int expected";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e2.line + ", column " + n.e2.column + ": int expected";
		  Errors.complain(s);
	  }
	  return new BooleanType();	
  }

  // Exp e1,e2;
  public Type visit(Plus n) {
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);
	  if ((t1==null) || (t2==null))
		  return new IntegerType();
	  if (!(t1 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e1.line + ", column " + n.e1.column + ": int expected";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e2.line + ", column " + n.e2.column + ": int expected";
		  Errors.complain(s);
	  }
	  return new IntegerType();
  }

  // Exp e1,e2;
  public Type visit(Minus n) {
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);
	  if ((t1==null) || (t2==null))
		  return new IntegerType();
	  if (!(t1 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e1.line + ", column " + n.e1.column + ": int expected";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e2.line + ", column " + n.e2.column + ": int expected";
		  Errors.complain(s);
	  }
	  return new IntegerType();
  }

  // Exp e1,e2;
  public Type visit(Times n) {
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);
	  if ((t1==null) || (t2==null))
		  return new IntegerType();
	  if (!(t1 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e1.line + ", column " + n.e1.column + ": int expected";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof IntegerType)){
		  String s = "Invalid expression at line " + n.e2.line + ", column " + n.e2.column + ": int expected";
		  Errors.complain(s);
	  }
	  return new IntegerType();
  }

  // Exp e1,e2;
  public Type visit(ArrayLookup n) {
	  Type t1 = n.e1.accept(this);
	  Type t2 = n.e2.accept(this);
	  if ((t1==null) || (t2==null))
		  return new IntegerType();
	  if (!(t1 instanceof IntArrayType)){
		  String s = "Invalid value at line " + n.e1.line + ", column " + n.e1.column + ": int expected at index";
		  Errors.complain(s);
	  }
	  if (!(t2 instanceof IntegerType)){
		  String s = "Invalid value at line " + n.e2.line + ", column " + n.e2.column + ": int expected";
		  Errors.complain(s);
	  }
	  return new IntegerType();
  }

  // Exp e;
  public Type visit(ArrayLength n) {
	  Type t = n.e.accept(this);
	  if (t == null){
		  return new IntegerType();
		}
	  if (!(t instanceof IntArrayType)) {
		  String s = "Invalid value at line " + n.e.line + ", column " + n.e.column + ": int[] expected";
		  Errors.complain(s);
	  }
	  return new IntegerType();
  }

  // Exp e;
  // Identifier i;
  // ExpList el;
  //TODO
  public Type visit(Call n) {
    Type t = n.e.accept(this);
    
    if (t == null)
		return null;
	if (!(t instanceof IdentifierType)) {
		String s = "Invalid value at line " + n.e.line + ", column " + n.e.column + ": object expected";
		Errors.complain(s);
		return null;
	}
	IdentifierType it = (IdentifierType)t;
	Identifier  id = new Identifier(it.s, 0, 0);
	if (!table.contains(id)){
		String s = "Undefined class " + n.i.toString() + " at line " + n.i.line + ", column " + n.i.column;
		Errors.complain(s);
		return null;
	}		
	
    for ( int i = 0; i < n.el.size(); i++ ) {
        n.el.elementAt(i).accept(this);
    }
    return null;
  }

  // int i;
  public Type visit(IntegerLiteral n) {
    return (new IntegerType());
  }

  public Type visit(True n) {
    return (new BooleanType());
  }

  public Type visit(False n) {
    return (new BooleanType());
  }

  // String s;
  public Type visit(IdentifierExp n) {
	  Type t = null;
	  Identifier i = new Identifier(n.s, 0, 0);
	  if(currClass.containsField(i)){
		  t = currClass.getField(i);
	  }
	  else if(currClass instanceof ClassExtended){
		  if(((ClassExtended)currClass).father.containsField(i)) {
			  t = ((ClassExtended)currClass).father.getField(i);
		  }
	  }
	  else if(currMethod.containsLocal(i)) {
		  t = currMethod.getLocal(i);
	  }
	  else if(currMethod.containsParam(i)) {
		  t = currMethod.getParam(i);
	  }
	  
	  if(t == null) {
		  String s = "Invalid use of " + n.s + " at line " + n.line + ", column " + n.column + ": variable not declared";
		  Errors.complain(s);
	  }
	  
	  return t;
	  /*else {
		  if(t instanceof IntegerType)
			  return new IntegerType();
		  else if(t instanceof IntArrayType)
			  return new IntArrayType();
		  else if(t instanceof BooleanType)
			  return new BooleanType();
		  else if(t instanceof IdentifierType)
			  return t;
	  }*/
  }

  //TODO
  public Type visit(This n) {
	  return new IdentifierType(currClass.name.s,n.line,n.column);
  }

  // Exp e;
  public Type visit(NewArray n) {
    Type t = n.e.accept(this);
	if(!(t instanceof IntegerType)){
		String s = "Invalid argument at line " + n.e.line + ", column " + n.e.column + ": int expected";
		Errors.complain(s);
	}
    return t;
  }

  // Identifier i;
  //TODO
  public Type visit(NewObject n) {
	  if(!table.contains(n.i)){
		  String s = "Undefined class " + n.i.toString() + " at line " + n.i.line + ", column " + n.i.column;
		  Errors.complain(s);
	  }
	  return new IdentifierType(n.i.s,n.i.line,n.i.column);
  }

  // Exp e;
  public Type visit(Not n) {
	  Type t= n.e.accept(this);
	  if (!(t instanceof BooleanType)) {
		  String s = "Invalid expression at line " + n.e.line + ", column " + n.e.column + ": boolean expected";
		  Errors.complain(s);
	  }
	  return new BooleanType();
  }

  // String s;
  public void visit(Identifier n) {
	  /*boolean contains = false;
	  System.out.println(n.s);
	  if(currClass.containsField(n)){
		  contains = true;
	  }
	  else if(currClass instanceof ClassExtended){
		  if(((ClassExtended)currClass).father.containsField(n)) {
			  contains = true;
		  }
	  }
	  else if(currMethod.containsLocal(n)) {
		  contains = true;
	  }
	  else if(currMethod.containsParam(n)) {
		  contains = true;
	  }
	  
	  if(!contains) {
		  String s = "Invalid use of " + n.s + " at line " + n.line + ", column " + n.column + ": variable not declared";
		  Errors.complain(s);
	  }*/
  }
}
